package com.powernode.service.impl;

import cn.hutool.http.HttpStatus;
import com.powernode.component.LoginUserHolder;
import com.powernode.constant.RabbitMQConstant;
import com.powernode.dto.ProductDto;
import com.powernode.dto.Result;
import com.powernode.entity.Order;
import com.powernode.entity.OrderAddress;
import com.powernode.entity.OrderItem;
import com.powernode.enums.OrderStatus;
import com.powernode.exception.BizException;
import com.powernode.feign.PMSFeign;
import com.powernode.mapper.OmsOrderMapper;
import com.powernode.service.OrderAddressService;
import com.powernode.service.OrderItemService;
import com.powernode.service.OrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author Juice
 * @since 2023-02-09
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OmsOrderMapper, Order> implements OrderService {

    @Autowired
    private PMSFeign feign;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private OrderAddressService orderAddressService;

    @Autowired
    private LoginUserHolder loginUserHolder;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 创建订单
     * 使用 @GlobalTransactional 开启分布式事务管理（seata）
     * 避免用户重复下单的两种方式：
     * 1. 在预览订单时生成一个唯一标识，对应同一个用户同一个订单。缓存到redis。在创建订单时验证前端传过来的token与redis存储的是否一致
     * 如果一致，证明是第一次下单。创建订单后将redis中的token删除。当用户重复下单时，由于redis中已经删除了token，所以此时比较肯定不一致
     * 返回错误提示信息即可
     * 2. 使用分布式环境下的锁，Redisson的RLock,在创建订单时，可以锁住当前用户，订单创建完再释放锁（过程中并不会影响其他用户下单）
     * Synchronize和Lock都是基于jvm的本地锁，在不同服务器上锁会失效
     */
    @Override
    //@Transactional
    @GlobalTransactional
    public Order createOrder(Order order) {
        BigDecimal totalPrice = new BigDecimal("0");
        // 先获取订单中的商品列表
        List<OrderItem> orderItemList = order.getOrderItems();
        // 处理每个商品
        for (OrderItem orderItem : orderItemList) {
            // 获取数据库中的最新商品信息
            Result<ProductDto> result = feign.getProductById(orderItem.getProductId());
            // 如果获取结果失败，抛异常
            if (result.getCode() != HttpStatus.HTTP_OK){
                throw new BizException(result.getCode(),result.getMessage());
            }
            // 获取商品
            ProductDto productDto = result.getData();
            if (productDto == null) {
                throw new BizException(HttpStatus.HTTP_NOT_FOUND,"商品不存在或已下架");
            }
            // 判断该商品价格是否更新
            if (productDto.getPrice().compareTo(orderItem.getPrice()) !=0) {
                throw new BizException(HttpStatus.HTTP_FORBIDDEN,"商品价格已更新，请重新下单");
            }
            // 更新商品信息
            orderItem.setImg(productDto.getImg());
            orderItem.setTitle(productDto.getTitle());
            // 合计价格
            totalPrice = totalPrice.add(orderItem.getPrice().multiply(new BigDecimal(orderItem.getNum())));
        }
        // 判断订单总价是否有更新
        if (totalPrice.compareTo(order.getTotalPrice()) !=0){
            throw new BizException(HttpStatus.HTTP_INTERNAL_ERROR,"商品价格已更新，请重新下单");
        }
        // 设置订单的用户
        order.setUserId(loginUserHolder.getLoginUserId());
        // 设置订单状态
        order.setStatus(OrderStatus.STATUS_1.getCode());
        // 保存到数据库
        baseMapper.insert(order);

        // 设置订单商品的订单id
        orderItemList.forEach(item -> item.setOrderId(order.getOrderId()));
        // 保存到数据库
        orderItemService.saveBatch(orderItemList);

        // 设置订单地址的订单id
        OrderAddress orderAddress = order.getOrderAddress();
        orderAddress.setOrderId(order.getOrderId());
        // 保存到数据库
        orderAddressService.save(orderAddress);

        // 扣减库存
        orderItemList.forEach(item -> {
            Result result = feign.decrStock(item.getProductId(), item.getNum());
            // 如果扣减失败，抛异常
            if(HttpStatus.HTTP_OK != result.getCode()){
                throw new BizException(result.getCode(), result.getMessage());
            }
        });

        //int i = 10/0;

        // 30分钟后发送消息到队列（延时发送），消费者接收到消息时判断用户是否下单，如果没有下单将订单状态设为取消支付，然后归还商品库存
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_DELAY,
                                        RabbitMQConstant.ROUTING_KEY_CANCEL_ORDER,
                                        order.getOrderId(),
                                        message ->
                                        {
                                            // 设置延迟发送的时间
                                             message.getMessageProperties().setDelay(1000*60*30);
                                             return message;
                                        });

        return order;
    }

    /**
     * 预览订单
     */
    @Override
    public Order previewOrder(Map<Long, Integer> map) {
        // 远程调用pms服务，根据商品id集合获取商品集合
        List<ProductDto> productDtoList = feign.listProductsByIds(new ArrayList<>(map.keySet())).getData();

        Order order = new Order();
        List<OrderItem> orderItemList = new ArrayList<>();

        for (ProductDto productDto : productDtoList) {
            OrderItem orderItem = new OrderItem();
            // 属性copy
            BeanUtils.copyProperties(productDto, orderItem);
            // 设置商品数量
            orderItem.setNum(map.get(productDto.getId()));
            // 计算小计
            orderItem.setTotalPrice(orderItem.getPrice().multiply(new BigDecimal(orderItem.getNum())));

            orderItemList.add(orderItem);
        }

        // 订单总计
        BigDecimal totalPrice = orderItemList
                                .stream()
                                .reduce(BigDecimal.ZERO, (sum, item) -> sum.add(item.getTotalPrice()), BigDecimal::add);
        // 设置订单总价格
        order.setTotalPrice(totalPrice);
        // 设置订单中的商品
        order.setOrderItems(orderItemList);
        return order;
    }
}
